# -*- encoding=utf-8 -*-
"""
# **********************************************************************************
# Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
# [oecp] is licensed under the Mulan PSL v1.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v1 at:
#     http://license.coscl.org.cn/MulanPSL
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v1 for more details.
# Author:
# Create: 2021-11-16
# Description: File encryption and decryption
# **********************************************************************************
"""
import os
import stat
import bz2
import time
import shutil
import hashlib
import tarfile
import logging
import configparser
from shutil import copy
from oecp.encrypt.ScanTools.src.tool.util.common_decode import DataParse
from oecp.utils.shell import shell_cmd

base_path = os.path.dirname(__file__)
save_path = os.path.join(base_path, "ScanTools/src/config/sign/")
config_path = os.path.join(base_path, "ScanTools/src/config/")
src_path = os.path.join(base_path, "ScanTools/src/")
check_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "conf/check.ini")
logger = logging.getLogger('oecp')


def get_list_dir(path):
    """
    Determine whether path is the full name of a file
    Args:
        path: file path
    Returns:

    """
    fl = []
    try:
        fl = os.listdir(path)
    except NotADirectoryError as error:
        pass
    finally:
        return fl


def get_all_file(path):
    """
    Get a list of all files and folder names in the current file
    Args:
        path:path

    Returns:

    """
    all_file = []
    if not os.path.exists(path):
        print("Please enter the correct path")
        return all_file

    files = get_list_dir(path)
    if len(files) != 0:
        files = list(map(lambda x: os.path.join(path, x), files))
        all_file = all_file + files
        for file in files:
            all_file = all_file + get_all_file(file)
    return all_file


def gen_hash_key(file):
    """
    Description: After traverse the values of the file
                 calculate the md5 encrypted hash value
    Args:
        file: file that need to be computed by hash values
    """
    try:
        hash_obj = hashlib.md5()
        with open(file, 'rb') as a_file:
            hash_obj.update(a_file.read())
    except (PermissionError, IsADirectoryError, FileNotFoundError) as err:
        return
    else:
        return hash_obj.hexdigest()


def generate_needed_file(input_path, output_path):
    """
    generate needed file
    Args:
        input_path: input path
        output_path: output path
    Returns:

    """
    hash_content = []
    if os.path.isdir(input_path):
        my_file_list = get_all_file(input_path)
        for file in my_file_list:
            file_key = gen_hash_key(file)
            if not file_key:
                continue
            hash_content.append(f"{file} : {file_key}")
    elif os.path.isfile(input_path):
        file_key = gen_hash_key(input_path)
        hash_content.append(f"{input_path} : {file_key}")
    with open(output_path, "w") as content:
        content.write("\r\n".join(hash_content))
    bz2_file = bz2.BZ2File(f"{output_path}.bz2", 'w')
    bz2_file.write("\r\n".join(hash_content).encode())
    bz2_file.close()


def encrypt_file(input_path, output_path):
    """
    encrypt generated md5 file
    Args:
        input_path: File path to be encrypted
        output_path: Encrypted file storage path
    Returns:

    """
    file_name = os.path.join(output_path, "tamper_proof_file.txt")
    if os.path.exists(file_name):
        os.remove(file_name)
    generate_needed_file(input_path, file_name)
    # Execute encryption software compilation script
    if not os.path.exists(f"{config_path}encode_gcm"):
        return "Encryption software is corrupted"
    os.chmod(f"{config_path}encode_gcm", stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
    cmd = [config_path + "encode_gcm", file_name + ".bz2", "1"]
    code, out, err = shell_cmd(cmd)
    if code:
        logger.warning(out)
        return err
    try:
        os.remove(file_name)
        os.remove(f"{file_name}.bz2")
    except (OSError, FileNotFoundError) as error:
        logger.warning(error)
    timestamp = time.strftime("%Y%m%d%H%M%S", time.localtime())
    with tarfile.open(f"{output_path}/report{timestamp}.tar.gz", "w:gz") as tar:
        tar.add(input_path, arcname=os.path.basename(input_path))


def decrypt_file(file_path, file_name="tamper_proof_file.txt.bz2.cipher"):
    """
    decrypt generated md5 file
    Args:
        file_name: Files that need to be decrypted
        file_path: Expected file storage path
    Returns:

    """
    if not os.path.exists(check_path):
        print("Please check the 'oecp/conf/check.ini' file.Confirm that the key_path is correct")
        return False
    config = configparser.ConfigParser()
    config.read(check_path)
    key_path = config.get("check", "key_path")
    decrypt_file_path = os.path.join(file_path, file_name)
    if not os.path.isfile(key_path):
        print("Please enter the correct key file name.eg: root/workdir/key_file.txt")
        return False
    key_file_name = key_path.split("/")[-1]
    copy(key_path, save_path)
    os.chmod(f"{config_path}decode_gcm.so", stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
    cmd = [config_path + "encode_gcm", save_path + f"{key_file_name}", "0"]
    code, out, err = shell_cmd(cmd)
    if code:
        logger.warning(err)
        return False
    DataParse.decode_config(src_path)
    str_data, decrypt_file_name = DataParse.parse_binary(decrypt_file_path)
    with open(f"{file_path}/{decrypt_file_name}.txt", "w") as content:
        content.write(str_data)
    try:
        os.remove(f"{save_path}{key_file_name}")
        os.remove(f"{save_path}{key_file_name}.cipher")
        return True
    except (OSError, FileNotFoundError) as error:
        logger.info(error)
        return False


def cmp_file(file1, file2):
    """
    Compare the contents of two files for differences
    Args:
        file1: file 1
        file2: file 2

    Returns:

    """
    with open(file1, 'r') as fp1, open(file2, 'r') as fp2:
        file_con_1 = fp1.readlines()
        file_con_2 = fp2.readlines()
        for idx, con in enumerate(file_con_1):
            if con.split(':')[-1] != file_con_2[idx].split(':')[-1]:
                return False
        return True


def decompression_package(name, dirs):
    """
    Unzip the tar package
    Args:
        name: Files name
        dirs: Expected file storage path
    Returns:

    """
    try:
        tar = tarfile.open(name)
        tar.extractall(path=dirs)
        return True
    except FileNotFoundError as error:
        logger.warning(error)
        return False


def check_result(file_path, file_name):
    """
    check result
    Args:
        file_name: Files that need to be checked
        file_path: Expected file storage path
    Returns:

    """
    try:
        temp_path = os.path.join(file_path, "tmp_dir")
        file_dir_name = file_path.split("/")[-1]
        untar_file_name = os.path.join(file_path, file_name)
        needed_file_path = os.path.join(temp_path, file_dir_name)
        decrypt_file_path = os.path.join(needed_file_path, "tamper_proof_file.txt.bz2.cipher")

        # Unzip the tar package
        if not decompression_package(untar_file_name, temp_path):
            return False

        copy(decrypt_file_path, temp_path)

        # decrypt generated md5 file
        if not decrypt_file(temp_path):
            return False

        encrypt_file_new = os.path.join(needed_file_path, "tamper_proof_file.txt")
        os.remove(os.path.join(needed_file_path, "tamper_proof_file.txt.bz2.cipher"))
        generate_needed_file(needed_file_path, encrypt_file_new)
        encrypt_file_old = os.path.join(temp_path, "tamper_proof_file.txt")
        return cmp_file(encrypt_file_old, encrypt_file_new)
    except (OSError, FileNotFoundError) as error:
        logger.warning(error)
        return False
    finally:
        shutil.rmtree(temp_path)
